#include "3dd.h"

G3::FONT Font1;
G3::TEXTBOX TextBox, TextBox2;

G3::FPS_COUNTER FPSCounter;
G3::TIMER MovementTimer;

G3::VEC3F CursorPosition;

G3::FRAME frame;

//==============================================================================
// Rotates around the Axis Base
//==============================================================================
void DD3RotateAround (double delta, G3::VEC3F axis) {
    G3::VEC3F vec = frame.Renderer.Camera.Position;

    G3::QUATERNIONF q1;

    q1.CreateFromAxisAngle (delta * G3CS_3DM_RotateSensitivity, axis);

	vec.Normalize ();
    vec = G3::ApplyQuat (vec, q1);

    frame.Renderer.Camera.LastPosition = frame.Renderer.Camera.Position;
    frame.Renderer.Camera.Position = vec * frame.Renderer.Camera.Position.GetLength ();

    frame.Renderer.Camera.FocusOn (G3::VEC3F (0, 0, 0));
}

//==============================================================================
// Mouse Click Handler
//==============================================================================
void DD3MouseClickHandler (G3::INPUT *Input, SDL_Event *Event, float aElapsedTime) {
    G3::GUI::PROFILER prof ("MouseClickHandler");

	// The user wishes to go into Ghost mode?
	if (Event->type == SDL_MOUSEBUTTONDOWN &&
		Event->button.button == SDL_BUTTON_RIGHT &&
        (Input->Keyboard.Modifiers & KMOD_CTRL)) {

		if (Input->Mouse.Mode == MOUSE_NORMAL)
			Input->Mouse.SetMode (MOUSE_CENTER);
		else
			frame.Input.Mouse.SetMode (MOUSE_NORMAL);
	}
}

void DD3WiimouseClickHandler (WIIMOUSE *Input, SDL_Event *Event, float aElapsedTime) {
    G3::GUI::PROFILER prof ("WiimouseClickHandler");

	if (Input == NULL)
		return;

	// The user wishes to go into Ghost mode?
	if (Event->type == SDL_MOUSEBUTTONDOWN &&
		Event->button.button == SDL_BUTTON_RIGHT &&
        (frame.Input.Keyboard.Modifiers & KMOD_CTRL)) {

		if (Input->Mode == MOUSE_NORMAL)
			Input->SetMode (MOUSE_CENTER);
		else
			Input->SetMode (MOUSE_NORMAL);
	}
}

//==============================================================================
// Mouse Move Handler
//==============================================================================
void DD3MouseMoveHandler (G3::INPUT *Input, SDL_Event *Event, float aElapsedTime) {
    G3::GUI::PROFILER prof ("MouseMoveHandler");

    if (!G3CS_3DM_UseNormalMouse || fopenDlg.Visible || fsaveDlg.Visible)
        return;

	G3::VEC3I MPos;
	G3::VEC3F BaseX, BaseY, BaseZ;

	SUBSPLINE sspl;

	if (G3CS_3DM_UseNormalMouse)
		SDL_GetMouseState (&MPos.x, &MPos.y);

    BaseX = Input->Frame->Renderer.Camera.GetRightVector ();
    BaseY = Input->Frame->Renderer.Camera.GetUpVector ();
    BaseZ = Input->Frame->Renderer.Camera.GetForwardVector ();

	CursorPosition = BaseX * (Input->Mouse.Position.x - Input->Mouse.ScreenCenter.x) * G3CS_3DM_PlanarSensitivity / ((float) Input->Frame->Width) +
                BaseY * (Input->Mouse.ScreenCenter.y - Input->Mouse.Position.y) * G3CS_3DM_PlanarSensitivity / ((float) Input->Frame->Height) +
                BaseZ * Input->Mouse.Position.z * G3CS_3DM_DepthSensitivity;

    // Lock the spline from rendering
    boost::mutex::scoped_lock lock( Spline.m_mutex );

    // Left mouse button down?
	if (Input->Mouse.State & SDL_BUTTON (SDL_BUTTON_LEFT)) {
        sspl.Radius = Spline.Radius;

        if (Spline.SubSplines.empty ()) {
            NumSubSplines++;
            Spline.SubSplines.push_back (sspl);
        }

        Spline.SubSplines [Spline.SubSplines.size () - 1].AddPoint (CursorPosition);

    // Rotation around the object
	} else if (Input->Mouse.State & SDL_BUTTON (SDL_BUTTON_RIGHT) && !(Input->Keyboard.Modifiers & KMOD_CTRL)) {
		// Rotate around the Y-axis only
		DD3RotateAround ((Input->Mouse.Position - Input->Mouse.LastPosition - Input->Mouse.ScreenCenter).x, G3::VEC3F (0, 1, 0));

    // Adds a spline
    } else {
        if (!Spline.SubSplines.empty () && !Spline.SubSplines [Spline.SubSplines.size () - 1].Points.empty ()) {
            sspl.Radius = Spline.Radius;

            NumSubSplines++;
            Spline.SubSplines.push_back (sspl);
        }
    }
}

void DD3WiimouseMoveHandler (WIIMOUSE *Input, SDL_Event *Event, float aElapsedTime) {
    G3::GUI::PROFILER prof ("WiimouseMoveHandler");

    if (G3CS_3DM_UseNormalMouse || fopenDlg.Visible || fsaveDlg.Visible || Input == NULL)
        return;

	G3::VEC3F BaseX, BaseY, BaseZ;

	SUBSPLINE sspl;

    BaseX = frame.Renderer.Camera.GetRightVector ();
    BaseY = frame.Renderer.Camera.GetUpVector ();
    BaseZ = frame.Renderer.Camera.GetForwardVector ();

	CursorPosition = BaseX * (Input->Position.x - Input->ScreenWidth) * G3CS_3DM_PlanarSensitivity / ((float) Input->ScreenWidth) +
                BaseY * (Input->ScreenHeight - Input->Position.y) * G3CS_3DM_PlanarSensitivity / ((float) Input->ScreenHeight) +
                BaseZ * Input->Position.z * G3CS_3DM_DepthSensitivity;

    // Lock the spline from rendering
    boost::mutex::scoped_lock lock( Spline.m_mutex );

    // Left mouse button down?
	if (Input->State & SDL_BUTTON (SDL_BUTTON_LEFT)) {
        sspl.Radius = Spline.Radius;

        if (Spline.SubSplines.empty ()) {
            NumSubSplines++;
            Spline.SubSplines.push_back (sspl);
        }

        Spline.SubSplines [Spline.SubSplines.size () - 1].AddPoint (CursorPosition);

    // Rotation around the object
	} else if (Input->State & SDL_BUTTON (SDL_BUTTON_RIGHT) && !(frame.Input.Keyboard.Modifiers & KMOD_CTRL)) {
		// Rotate around the Y-axis only
		DD3RotateAround ((Input->Position - Input->LastPosition - Input->ScreenCenter).x, G3::VEC3F (0, 1, 0));

    // Adds a spline
    } else {
        if (!Spline.SubSplines.empty () && !Spline.SubSplines [Spline.SubSplines.size () - 1].Points.empty ()) {
            sspl.Radius = Spline.Radius;

            NumSubSplines++;
            Spline.SubSplines.push_back (sspl);
        }
    }
}


//==============================================================================
// Mouse Button Up Handler
//==============================================================================
void DD3MouseUpHandler (G3::INPUT *Input, SDL_Event *Event, float aElapsedTime) {
}

void DD3WiimouseUpHandler (WIIMOUSE *Input, SDL_Event *Event, float aElapsedTime) {
}

//==============================================================================
// Mouse Wheel Handler
//==============================================================================
void DD3MouseWheelHandler (G3::INPUT *Input, SDL_Event *Event, float Wheel, float aElapsedTime) {
    if (fopenDlg.Visible || fsaveDlg.Visible)
        return;

    DD3MouseMoveHandler (Input, NULL, aElapsedTime);
}

//==============================================================================
// Update Handler
//==============================================================================
void DD3UpdateHandler (G3::INPUT *Input, float aElapsedTime) {
    G3::GUI::PROFILER prof ("UpdateHandler");

    if (!fopenDlg.Visible && !fsaveDlg.Visible) {
        if (Input->Keyboard.IsKeyDown (SDLK_a))
            Input->Frame->Renderer.Camera.Move (G3::VEC3F (0.0f, 1.0f, 0.0f) * aElapsedTime);
        if (Input->Keyboard.IsKeyDown (SDLK_z))
            Input->Frame->Renderer.Camera.Move (G3::VEC3F (0.0f, -1.0f, 0.0f) * aElapsedTime);
        if (Input->Keyboard.IsKeyDown (SDLK_LEFT))
            Input->Frame->Renderer.Camera.Move (G3::VEC3F (-1.0f, 0.0f, 0.0f) * aElapsedTime);
        if (Input->Keyboard.IsKeyDown (SDLK_RIGHT))
            Input->Frame->Renderer.Camera.Move (G3::VEC3F (1.0f, 0.0f, 0.0f) * aElapsedTime);
        if (Input->Keyboard.IsKeyDown (SDLK_UP))
            Input->Frame->Renderer.Camera.Move (G3::VEC3F (0.0f, 0.0f, 1.0f) * aElapsedTime);
        if (Input->Keyboard.IsKeyDown (SDLK_DOWN))
            Input->Frame->Renderer.Camera.Move (G3::VEC3F (0.0f, 0.0f,-1.0f) * aElapsedTime);

        // If the camera moved, then mouse also moved in respect to the model
        DD3MouseMoveHandler (Input, NULL, aElapsedTime);
		DD3WiimouseMoveHandler (&wiicom.wiimouse, NULL, aElapsedTime);
    }

	if (Input->Keyboard.IsKeyDown (SDLK_ESCAPE)) {
        gluDeleteQuadric (cylinder);
        gluDeleteQuadric (sphere);
		Input->SendQuit ();
    }

	// Rotate around the object
	DD3RotateAround (rot_magnitude * aElapsedTime * G3CS_WII_NunchukRotateSensitivity, G3::VEC3F (0, rot_angle.y, 0));
}

//==============================================================================
// Keyboard Handler
//==============================================================================
void DD3KeyHandler (G3::INPUT *Input, SDL_keysym* keysym, float aElapsedTime) {
    G3::GUI::PROFILER prof ("KeyHandler");

    if (fopenDlg.Visible || fsaveDlg.Visible)
        return;

    // Lock the spline from rendering
    boost::mutex::scoped_lock lock( Spline.m_mutex );

    if (Input->Keyboard.Modifiers & KMOD_CTRL) {
        // Ctrl + S ?
       	if (keysym->sym == SDLK_s) {
            fsaveDlg.Show ();
       	}

        // Ctrl + O ?
       	if (keysym->sym == SDLK_o) {
            Spline.SubSplines.clear ();

			frame.Renderer.Camera.Reset (G3CS_Camerapos);
			frame.Renderer.Camera.FocusOn (G3::VEC3F (0, 0, 0));

            fopenDlg.Show ();
       	}

        // Ctrl + N ?
       	if (keysym->sym == SDLK_n)
            StartNew();
    } else {
        if (keysym->sym == SDLK_p)
            ToggleDrawMode();
    	if (keysym->sym == SDLK_h)
        	G3::GUI::MainStats.Visible ^= 1;
    	if (keysym->sym == SDLK_n && G3::GUI::MainStats.Visible)
        	G3::GUI::MainStats.NextPage ();
    }
}

void DD3RenderTinyAxis () {
    bool Lighting = false;
    bool Texture = false;
	bool DepthCheck = false;

    glPushMatrix ();
    if (glIsEnabled (GL_TEXTURE_2D)){ glDisable (GL_TEXTURE_2D); Texture = true; }
    if (glIsEnabled (GL_LIGHTING)){ glDisable (GL_LIGHTING); Lighting = true; }

	// Valgrind: Invalid write of size 4
    glEnable (GL_LINE_SMOOTH);
    glLineWidth (2.0f);

    glBegin (GL_LINES);
    //X:R
    glColor3f (0.5f, 0, 0);
    glVertex3f (0, 0, 0);
    glVertex3f (1, 0, 0);

    //Y:G
    glColor3f (0, 0.5f, 0);
    glVertex3f (0, 0, 0);
    glVertex3f (0, 1, 0);

    //Z:B
    glColor3f (0, 0, 0.5f);
    glVertex3f (0, 0, 0);
    glVertex3f (0, 0, 1);
    glEnd ();

    glLineWidth (1.0f);
    glDisable (GL_LINE_SMOOTH);

    glColor3f (1, 1, 1);

    if (Texture) { glEnable (GL_TEXTURE_2D); }
    if (Lighting) { glEnable (GL_LIGHTING); }
    glPopMatrix ();
}

void init_wiimote() {
    G3::MainLog.Report ("Configuring input devices..\n");
	// Set mouse input to normal mode (the alternative is to center it)
	frame.Input.Mouse.SetMode (MOUSE_NORMAL);

	wiicom.wiimouse.SetScreenSize (G3CS_Resolution.x, G3CS_Resolution.y);
	wiicom.wiimouse.Center ();
	wiicom.wiimouse.SetMode (MOUSE_NORMAL);
	wiicom.start (1);

    G3::MainLog.Report ("Attaching handlers to signals..\n");
	// Attach handlers to some input signals
	frame.Input.doOnUpdate (DD3UpdateHandler);
	frame.Input.doOnKeyDown (DD3KeyHandler);
	frame.Input.doOnMouseDown (DD3MouseClickHandler);
	frame.Input.doOnMouseUp (DD3MouseUpHandler);
	frame.Input.doOnMouseMove (DD3MouseMoveHandler);
	frame.Input.doOnMouseWheel (DD3MouseWheelHandler);
	wiicom.doOnWiimouseDown (DD3WiimouseClickHandler);
	wiicom.doOnWiimouseUp (DD3WiimouseUpHandler);
	wiicom.doOnWiimouseMove (DD3WiimouseMoveHandler);
}

void init_spline() {
	Spline.PSys.Load ("particle_systems/float_still.lua");
	Spline.PSys.Init ();
}

//==============================================================================
// Main method
//==============================================================================
int main (int argc, char **argv) {
    // Load configuration script
    LoadConfig ("3dd.cfg");

	// Set the spline radius
    Spline.Radius = G3CS_SplineWidth;

    G3::MainLog.Report ("Creating a window..\n");

	// Create the OpenGL window with the requested resolution
	frame.Create ("G3ngine:: 3dd", G3CS_Resolution.x, G3CS_Resolution.y, G3CS_ColDepth,
                  (G3CS_FullScreen * SDL_FULLSCREEN));

    G3::MainLog.Report ("Initializing the renderer..\n");

	// Create the renderer with the same viewport dimensions
	frame.Renderer.Create (G3CS_Resolution.x, G3CS_Resolution.y);
	// And set up a perspective
	frame.Renderer.SetPerspective (G3CS_Resolution.x, G3CS_Resolution.y);

	// Reset camera to a default position and focus it on the Axis Base
	frame.Renderer.Camera.Reset (G3CS_Camerapos);
	frame.Renderer.Camera.FocusOn (G3::VEC3F (0, 0, 0));

	// Create the needed quadrics for spline rendering
    cylinder = gluNewQuadric ();
	gluQuadricNormals (cylinder, GLU_SMOOTH);
    sphere = gluNewQuadric ();
	gluQuadricNormals (sphere, GLU_SMOOTH);

    /* Enable smooth shading */
    glShadeModel (GL_SMOOTH);

    /* Set the background color */
    glClearColor (G3CS_Bg_Color.r, G3CS_Bg_Color.g, G3CS_Bg_Color.b, 0.0f);

    /* Depth buffer setup */
    glClearDepth (1.0f);

    /* The Type Of Depth Test To Do */
    glDepthFunc (GL_LEQUAL);

    /* Enable 2D Texture Mapping */
    glEnable (GL_TEXTURE_2D);

//    float lightpos[4] = {G3CS_Lightpos.x, G3CS_Lightpos.y, G3CS_Lightpos.z, 1.0f};
//    glLightfv (GL_LIGHT0, GL_POSITION, lightpos);

    glEnable (GL_LIGHTING);
    glEnable (GL_LIGHT0);

    init_wiimote();

    G3::MainLog.Report ("Mounting media directory..\n");
	// Mount a media directory for fonts / textures
	G3::MainFilesys.MountDir ("../../media/font", "");
	G3::MainFilesys.MountDir ("../../media", "");

    G3::MainLog.Report ("Loading the default font..\n");
	// Load the default font
	Font1.Load ("font.lua");

	// Initialize on-screen textboxes
	TextBox.Font = &Font1;
	TextBox2.Font = &Font1;
	TextBox.Position = G3::VEC2I (10, 10);
	TextBox2.Position = G3::VEC2I (10, 22);

	init_spline();

    G3::MainLog.Report ("Setting up Stats pages..\n");

    // Create a help Stats page
    G3::GUI::STATS_PAGE statsPage1 (&Font1, "Help", G3::RECTI (3, 100, 637, 477));
    G3::GUI::STATS_LABEL statsHelpLine1 ("Key combinations");
    G3::GUI::STATS_LABEL statsHelpLine2 ("  Arrows, nunchuk and LMB rotate the camera");
    G3::GUI::STATS_LABEL statsHelpLine3 ("  A/Z move the camera up/down");
    G3::GUI::STATS_LABEL statsHelpLine4 ("  P switches between torus/particles");
    G3::GUI::STATS_LABEL statsHelpLine5 ("  Ctrl+S saves the spline");
    G3::GUI::STATS_LABEL statsHelpLine6 ("  Ctrl+O loads a spline");
    G3::GUI::STATS_LABEL statsHelpLine7 ("  Ctrl+N resets the spline");
    // Add the lines
    statsPage1.AddEntry (&statsHelpLine1);
	statsPage1.AddEntry (&statsHelpLine2);
	statsPage1.AddEntry (&statsHelpLine3);
	statsPage1.AddEntry (&statsHelpLine4);
    statsPage1.AddEntry (&statsHelpLine5);
	statsPage1.AddEntry (&statsHelpLine6);
	statsPage1.AddEntry (&statsHelpLine7);

	// Create a standard Stats page
	G3::GUI::STATS_PAGE statsPage2 (&Font1, "Rendering information", G3::RECTI (3, 100, 637, 477));
	G3::GUI::STATS_INT_PTR statsNumPointsRendered ("Num points rendered", &NumPointsRendered);
	G3::GUI::STATS_INT_PTR statsNumLinesRendered ("Num lines rendered", &NumLinesRendered);
	G3::GUI::STATS_INT_PTR statsNumSegmentsRendered ("Num segments rendered", &NumSegmentsRendered);
	G3::GUI::STATS_INT_PTR statsNumSubSplines ("Num subsplines", &NumSubSplines);
	// Attach some variables to it
	statsPage2.AddEntry (&statsNumPointsRendered);
	statsPage2.AddEntry (&statsNumLinesRendered);
	statsPage2.AddEntry (&statsNumSegmentsRendered);
	statsPage2.AddEntry (&statsNumSubSplines);

	// Add the Stats pages to GUI
	G3::GUI::MainStats.AddPage (&statsPage1);
	G3::GUI::MainStats.AddPage (&statsPage2);
	// Create a Profiling Stats page aswell - turn it off for performance reasons (should only be used for debugging)
	G3::GUI::MainProfilerMgr.AddStatsPage (&Font1, G3::RECTI (3, 100, 637, 477));
	G3::GUI::MainProfilerMgr.profilingEnabled = G3CS_Profiling;
	// Hide these pages at first
	G3::GUI::MainStats.Hide ();

	// Create the file dialogs
    G3::RECTI sdlgr (50, 60, 260, 170);
	fopenDlg.Create (sdlgr, &Font1, "Load a spline..", false);
	fsaveDlg.Create (sdlgr, &Font1, "Save the spline..", true);

    // Create joystick gauge
	joy_gauge.Rect = G3::RECTI (G3CS_Resolution.x - 100, G3CS_Resolution.y - 100, G3CS_Resolution.x - 5, G3CS_Resolution.y - 5);
	joy_gauge.mag = 0.5f;
	joy_gauge.ang = 45.0f;
	joy_gauge.PreCalc ();

	G3::GUI::MainGui.AddWidget (&G3::GUI::MainGui.Root, &joy_gauge);

	// Create draw mode indicator image
	hFogMode = G3::TextureManager.GetHandle ("icons/3dd_fog.png");
    hTorusMode = G3::TextureManager.GetHandle ("icons/3dd_torus.png");
	DrawModeIndicator.Create (G3::RECTI(G3CS_Resolution.x - 232, G3CS_Resolution.y - 82, G3CS_Resolution.x - 200, G3CS_Resolution.y - 50), UseParticles ? hFogMode : hTorusMode);

	G3::GUI::MainGui.AddWidget (&G3::GUI::MainGui.Root, &DrawModeIndicator);

	// Attach the GUI an input handler
	G3::GUI::MainGui.AttachInput (&frame.Input);

	G3::MainLog.Report ("Starting timers..\n");
	// Start FPS and movement timers
	FPSCounter.Start ();
	MovementTimer.Start ();

	G3::MainLog.Report ("Starting main loop..\n");

	while (true) {
        G3::GUI::PROFILER prof ("Main Loop");

		// Update general input data
		frame.Input.GetInput ();

		// Clear the active buffer
        frame.Renderer.ClearBuffer ();

		// Mark the start of rendering for FPS calculation
		FPSCounter.CheckTime ();

		// Look around with the mouse
        frame.Renderer.Look ();
		// Show the grid if the configuration script asked us to
        if (G3CS_Display_Grid)
            frame.Renderer.RenderGrid ();
		else if (G3CS_DisplayAxis)
			DD3RenderTinyAxis ();

		// Render the whole spline
    	Spline.Render ( frame.Input.GetElapsedTime () );


		glDisable (GL_TEXTURE_2D);
		glDisable (GL_LIGHTING);
		glColor3f (0.0f, 1.0f, 0.0f);

		glPushMatrix ();
		    glTranslatef (CursorPosition.x, CursorPosition.y, CursorPosition.z);
		    gluSphere (sphere, Spline.Radius, G3CS_Subdivisions, G3CS_Subdivisions);
		glPopMatrix ();

		glEnable (GL_TEXTURE_2D);
		glEnable (GL_LIGHTING);

		// Display current FPS
		// And wiimote status
		if (!G3CS_3DM_UseNormalMouse) {
			TextBox2.glPrint ("Status: BAT %.0f: %s", battery_level * 100.0f, wiicom.status.c_str ());
			if (motion_sensing_on)
				TextBox.glPrint ("FPS: %lf  Motion sensing is enabled", FPSCounter.FPS);
			else
				TextBox.glPrint ("FPS: %lf  Motion sensing is disabled", FPSCounter.FPS);
		} else {
			TextBox.glPrint ("FPS: %lf", FPSCounter.FPS);
			TextBox2.glPrint ("Status: Normal mouse mode");
		}

		// Draw the GUI
    	G3::GUI::MainGui.Draw (0.0f);

		// Swap the buffers
        frame.Renderer.SwapBuffer ();
		// Mark the end of rendering
		FPSCounter.AddFrame ();

        // Keep the main thread from consuming all the resources
        // TODO:: An automatic FPS regulation system needs to be devised
		SDL_Delay (10);
	}

	return 0;
}
